using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Research.Kinect.Nui;



namespace CMK
{
    /// <summary>
    /// Dies ist der Haupttyp für Ihr Spiel
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Array myObjects;
        private SpriteFont myFont;
        private SkeletonFrame lastSkeletonFrame;

        Texture2D bgTexture;

        private static Boolean DEBUG = true;
        private static Boolean KINECT = true;
        private static Boolean SKELETON = true;
        private static Boolean DEPTH = true;

        private static Boolean FULLSCREEN = true;
        private static Boolean BACKGROUND_TEST = true;

        private static int WIDTH = 800;
        private static int HEIGHT = 600;
        private static int NUM_PLAYERS = 8;
        private static int OBJECT_WIDTH = 30;

#if DEBUG
        float[] max = new float[3];
        float[] min = new float[3];
#endif

        private Runtime kinectRuntime;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = WIDTH;
            graphics.PreferredBackBufferHeight = HEIGHT;
            graphics.IsFullScreen = FULLSCREEN;
            //graphics.ApplyChanges();
        }

        /// <summary>
        /// Ermöglicht dem Spiel die Durchführung einer Initialisierung, die es benötigt, bevor es ausgeführt werden kann.
        /// Dort kann es erforderliche Dienste abfragen und nicht mit der Grafik
        /// verbundenen Content laden.  Bei Aufruf von base.Initialize werden alle Komponenten aufgezählt
        /// sowie initialisiert.
        /// </summary>
        protected override void Initialize()
        {
            myObjects = new Player[NUM_PLAYERS];

            if (KINECT)
            {
                lastSkeletonFrame = null;
                try
                {
                    kinectRuntime = Runtime.Kinects[0];
                    kinectRuntime.Initialize(RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseDepthAndPlayerIndex);//RuntimeOptions.UseDepthAndPlayerIndex | R
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Runtime initialization failed. Please make sure Kinect device is plugged in.\n" + ex.Message);
                    return;
                }

                if (DEPTH)
                {

                    kinectRuntime.DepthFrameReady += DepthFrameReady;
                    kinectRuntime.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);
                }

                if (SKELETON)
                {
                    kinectRuntime.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);

                    var parameters = new TransformSmoothParameters
                    {
                        Smoothing = 0.75f,
                        Correction = 0.5f,
                        Prediction = 0.05f,
                        JitterRadius = 0.05f,
                        MaxDeviationRadius = 0.04f
                    };
                    kinectRuntime.SkeletonEngine.SmoothParameters = parameters;
                    kinectRuntime.SkeletonEngine.TransformSmooth = true;
                }
            }
            base.Initialize();
        }

        //--------------------------DEPTH IMAGE------------------------------------
        private void DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            if (BACKGROUND_TEST)
            {
                //Build up Arrays for the depth, der Player-Index, and the Player-Depth-Image
                int[] depth = new int[e.ImageFrame.Image.Width * e.ImageFrame.Image.Height];
                int[] player = new int[e.ImageFrame.Image.Width * e.ImageFrame.Image.Height];
                int[] playercoded = new int[e.ImageFrame.Image.Width * e.ImageFrame.Image.Height];

                for (int i = 0; i < depth.Length; i++)
                {
                    //The first three Bits of every second Byte are for the player index
                    player[i] = e.ImageFrame.Image.Bits[i * 2] & 0x07;
                    //The next 13 Bits are for the depth-Image
                    depth[i] = (e.ImageFrame.Image.Bits[i * 2 + 1] << 5) | (e.ImageFrame.Image.Bits[i * 2] >> 3);

                    if (depth[i] > 2500 && depth[i] < 2700)
                    {
                        playercoded[i] = (int)Color.White.PackedValue;
                    }
                    else if (player[i] != 0)
                    {
                        playercoded[i] = (int)Color.Red.PackedValue;
                    }
                    else
                    {
                        playercoded[i] = 0;
                    }
                }

                bgTexture = new Texture2D(GraphicsDevice, e.ImageFrame.Image.Width, e.ImageFrame.Image.Height);
                bgTexture.SetData(playercoded);
            }
        }
        //--------------------------SKELETON------------------------------------

        void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            lastSkeletonFrame = e.SkeletonFrame;
        }

        /// <summary>
        /// LoadContent wird einmal pro Spiel aufgerufen und ist der Platz, wo
        /// Ihr gesamter Content geladen wird.
        /// </summary>
        protected override void LoadContent()
        {
            // Erstellen Sie einen neuen SpriteBatch, der zum Zeichnen von Texturen verwendet werden kann.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            myFont = Content.Load<SpriteFont>(@"SpriteFont1");

            for (int i = 0; i < NUM_PLAYERS; i++)
            {
                //Hidden via xvalue and visible attribut
                myObjects.SetValue(new Player(new Vector3(0, 0, 0), OBJECT_WIDTH, 1f, i), i);
                (myObjects.GetValue(i) as Player).setTexture(this.Content.Load<Texture2D>("circle" + i % 2));

            }

            if (!KINECT)
            {
                (myObjects.GetValue(0) as Player).setVisible(1f);
            }

            //TODO - Eventuell weiteren Content laden
        }

        /// <summary>
        /// UnloadContent wird einmal pro Spiel aufgerufen und ist der Ort, wo
        /// Ihr gesamter Content entladen wird.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Entladen Sie jeglichen Nicht-ContentManager-Content hier
        }

        /// <summary>
        /// Ermöglicht dem Spiel die Ausführung der Logik, wie zum Beispiel Aktualisierung der Welt,
        /// Überprüfung auf Kollisionen, Erfassung von Eingaben und Abspielen von Ton.
        /// </summary>
        /// <param name="gameTime">Bietet einen Schnappschuss der Timing-Werte.</param>
        protected override void Update(GameTime gameTime)
        {
            // Ermöglicht ein Beenden des Spiels
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            {
                if (DEBUG)
                {
                    Console.WriteLine(getMinMax());
                }
                this.Exit();
            }

            if (!KINECT)
            {
                (myObjects.GetValue(0) as Player).setPosition(new Vector3(Mouse.GetState().X, Mouse.GetState().Y, 30));
            }

            if (KINECT)
            {

                //Hide all Circles-TODO
                for (int i = 0; i < NUM_PLAYERS; i++)
                {
                    (myObjects.GetValue(i) as Player).setVisible(1f);
                }

                if (lastSkeletonFrame != null)
                {
                    foreach (SkeletonData skeleton in lastSkeletonFrame.Skeletons)
                    {
                        int userindex = skeleton.UserIndex & 0x07;
                        if (SkeletonTrackingState.Tracked == skeleton.TrackingState || SkeletonTrackingState.PositionOnly == skeleton.TrackingState)
                        {
                            // 7 Bit user id (101 = 5)
                            if (myObjects.GetValue(userindex) != null)
                            {
                                (myObjects.GetValue(userindex) as Player).setPosition(new Vector3(skeleton.Position.X, skeleton.Position.Y, skeleton.Position.Z));
                                (myObjects.GetValue(userindex) as Player).setVisible(1f);
                            }
                            #region Debug
                            if (DEBUG)
                            {
                                if (skeleton.Position.X > max[0])
                                {
                                    max[0] = skeleton.Position.X;
                                }

                                if (skeleton.Position.Y > max[1])
                                {
                                    max[1] = skeleton.Position.Y;
                                }

                                if (skeleton.Position.Z > max[2])
                                {
                                    max[2] = skeleton.Position.Z;
                                }

                                if (skeleton.Position.X < min[0])
                                {
                                    min[0] = skeleton.Position.X;
                                }

                                if (skeleton.Position.Y < min[1])
                                {
                                    min[1] = skeleton.Position.Y;
                                }

                                if (skeleton.Position.Z < min[2])
                                {
                                    min[2] = skeleton.Position.Z;
                                }
                            }
                            #endregion
                        }
                        else
                        {
                            (myObjects.GetValue(userindex) as Player).setPosition(new Vector3(0, 0, 0));
                        }
                    }
                }
                (myObjects.GetValue(0) as Player).setPosition(new Vector3(Mouse.GetState().X, Mouse.GetState().Y, 30));
            }
            base.Update(gameTime);
        }

        protected String getMinMax()
        {
            String debug_txt = "";

            for (int i = 0; i < min.Length; i++)
            {
                debug_txt += min[i] + " ";
            }
            debug_txt += " --- ";
            for (int i = 0; i < max.Length; i++)
            {
                debug_txt += max[i] + " ";
            }

            return debug_txt;
        }

        /// <summary>
        /// Dies wird aufgerufen, wenn das Spiel selbst zeichnen soll.
        /// </summary>
        /// <param name="gameTime">Bietet einen Schnappschuss der Timing-Werte.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);

            spriteBatch.Begin();

            if (BACKGROUND_TEST && bgTexture != null)
            {
                spriteBatch.Draw(bgTexture, new Rectangle(0, 0, GraphicsDevice.DisplayMode.Width, GraphicsDevice.DisplayMode.Height), null, Color.White, 0, new Vector2(), SpriteEffects.None, 1);
            }

            for (int i = 0; i < myObjects.Length; i++)
            {
                if (myObjects.GetValue(i) != null)
                {
                    //if ((myObjects.GetValue(i) as Player).getPlayerIndex() != 0)
                    //{
                        if (KINECT)
                        {
                            //TODO: Develop a proper Algorithm
                            float xvalue = ((myObjects.GetValue(i) as Player).getPosition().X + 1f) * WIDTH / 2f; // getposition between -1 and 1
                            float yvalue = ((myObjects.GetValue(i) as Player).getPosition().Y + 1f) * 2f; // getposition between -1 and 1
                            float zvalue = ((myObjects.GetValue(i) as Player).getPosition().Z - 0.5f) * HEIGHT / 3.5f; //getposition between 0.5 and 5

                            if ((myObjects.GetValue(i) as Player).getPosition().X == 0f)
                            {
                                xvalue = yvalue = zvalue = 5;
                            }

                            // TODO Draw nicer Drawables 
                            spriteBatch.Draw((myObjects.GetValue(i) as Player).getTexture(), new Rectangle((int)xvalue - OBJECT_WIDTH / 2, (int)zvalue - OBJECT_WIDTH / 2, (int)(OBJECT_WIDTH * yvalue), (int)(OBJECT_WIDTH * yvalue)), null, Color.White, 0, new Vector2(), SpriteEffects.None, 1);
                            //Console.WriteLine(i+" "+(myObjects.GetValue(i) as Player).getVisible());
                            //Console.WriteLine((myObjects.GetValue(i) as Player).getPosition().Z);
                            spriteBatch.Draw((myObjects.GetValue(0) as Player).getTexture(), new Rectangle((int)(myObjects.GetValue(0) as Player).getPosition().X - OBJECT_WIDTH / 2, (int)(myObjects.GetValue(0) as Player).getPosition().Y - OBJECT_WIDTH / 2, OBJECT_WIDTH, OBJECT_WIDTH), null, Color.White, 0, new Vector2(), SpriteEffects.None, 1);
                        }
                        if (!KINECT)
                        {
                            spriteBatch.Draw((myObjects.GetValue(0) as Player).getTexture(), new Rectangle((int)(myObjects.GetValue(0) as Player).getPosition().X - OBJECT_WIDTH / 2, (int)(myObjects.GetValue(0) as Player).getPosition().Y - OBJECT_WIDTH / 2, OBJECT_WIDTH, OBJECT_WIDTH), null, Color.White, 0, new Vector2(), SpriteEffects.None, 1);
                        }
                    //}
                }
                else
                {
                    Console.WriteLine("null");
                }

            }
            if (DEBUG)
            {

                spriteBatch.DrawString(myFont, "DEBUG: " + getMinMax(), new Vector2(0, HEIGHT - 25), Color.White);
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
